home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / imap / non-ANSI / c-client / rfc822.c < prev    next >
C/C++ Source or Header  |  1996-04-09  |  59KB  |  1,775 lines

  1. /*
  2.  * Program:    RFC-822 routines (originally from SMTP)
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    27 July 1988
  13.  * Last Edited:    8 April 1996
  14.  *
  15.  * Sponsorship:    The original version of this work was developed in the
  16.  *        Symbolic Systems Resources Group of the Knowledge Systems
  17.  *        Laboratory at Stanford University in 1987-88, and was funded
  18.  *        by the Biomedical Research Technology Program of the National
  19.  *        Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University
  22.  * Copyright 1996 by the University of Washington
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43.  
  44.  
  45. #include <ctype.h>
  46. #include <stdio.h>
  47. #include <time.h>
  48. #include "mail.h"
  49. #include "osdep.h"
  50. #include "rfc822.h"
  51. #include "misc.h"
  52.  
  53. /* RFC-822 static data */
  54.  
  55.  
  56. char *errhst = ".SYNTAX-ERROR."; /* syntax error host string */
  57.  
  58.  
  59. /* Body formats constant strings, must match definitions in mail.h */
  60.  
  61. char *body_types[TYPEMAX+1] = {
  62.   "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
  63.   "X-UNKNOWN"
  64. };
  65.  
  66.  
  67. char *body_encodings[ENCMAX+1] = {
  68.   "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
  69. };
  70.  
  71.  
  72. /* Token delimiting special characters */
  73.  
  74.                 /* full RFC-822 specials */
  75. const char *rspecials =  "()<>@,;:\\\"[].";
  76.                 /* body token specials */
  77. const char *tspecials = " ()<>@,;:\\\"[]./?=";
  78.  
  79.  
  80. /* Once upon a time, CSnet had a mailer which assigned special semantics to
  81.  * dot in e-mail addresses.  For the sake of that mailer, dot was added to
  82.  * the RFC-822 definition of `specials', even though it had numerous bad side
  83.  * effects:
  84.  *   1)    It broke mailbox names on systems which had dots in user names, such as
  85.  *    Multics and TOPS-20.  RFC-822's syntax rules require that `Admin . MRC'
  86.  *    be considered equivalent to `Admin.MRC'.  Fortunately, few people ever
  87.  *    tried this in practice.
  88.  *   2) It required that all personal names with an initial be quoted, a widely
  89.  *    detested user interface misfeature.
  90.  *   3)    It made the parsing of host names be non-atomic for no good reason.
  91.  * To work around these problems, the following alternate specials lists are
  92.  * defined.  hspecials and wspecials are used in lieu of rspecials, and
  93.  * ptspecials are used in lieu of tspecials.  These alternate specials lists
  94.  * make the parser work a lot better in the real world.  It ain't politically
  95.  * correct, but it lets the users get their job done!
  96.  */
  97.  
  98.                 /* parse-host specials */
  99. const char *hspecials = " ()<>@,;:\\\"";
  100.                 /* parse-word specials */
  101. const char *wspecials = " ()<>@,;:\\\"[]";
  102.                 /* parse-token specials for parsing */
  103. const char *ptspecials = " ()<>@,;:\\\"[]/?=";
  104.  
  105. /* RFC822 writing routines */
  106.  
  107.  
  108. /* Write RFC822 header from message structure
  109.  * Accepts: scratch buffer to write into
  110.  *        message envelope
  111.  *        message body
  112.  */
  113.  
  114. void rfc822_header (header,env,body)
  115.     char *header;
  116.     ENVELOPE *env;
  117.     BODY *body;
  118. {
  119.   if (env->remail) {        /* if remailing */
  120.     long i = strlen (env->remail);
  121.                 /* flush extra blank line */
  122.     if (i > 4 && env->remail[i-4] == '\015') env->remail[i-2] = '\0';
  123.     strcpy (header,env->remail);/* start with remail header */
  124.   }
  125.   else *header = '\0';        /* else initialize header to null */
  126.   rfc822_header_line (&header,"Newsgroups",env,env->newsgroups);
  127.   rfc822_header_line (&header,"Date",env,env->date);
  128.   rfc822_address_line (&header,"From",env,env->from);
  129.   rfc822_address_line (&header,"Sender",env,env->sender);
  130.   rfc822_address_line (&header,"Reply-To",env,env->reply_to);
  131.   rfc822_header_line (&header,"Subject",env,env->subject);
  132.   if (env->bcc && !(env->to || env->cc))
  133.     strcat (header,"To: undisclosed recipients: ;\015\012");
  134.   rfc822_address_line (&header,"To",env,env->to);
  135.   rfc822_address_line (&header,"cc",env,env->cc);
  136. /* bcc's are never written...
  137.  * rfc822_address_line (&header,"bcc",env,env->bcc);
  138.  */
  139.   rfc822_header_line (&header,"In-Reply-To",env,env->in_reply_to);
  140.   rfc822_header_line (&header,"Message-ID",env,env->message_id);
  141.   rfc822_header_line (&header,"Followup-to",env,env->followup_to);
  142.   rfc822_header_line (&header,"References",env,env->references);
  143.   if (body && !env->remail) {    /* not if remail or no body structure */
  144.     strcat (header,"MIME-Version: 1.0\015\012");
  145.     rfc822_write_body_header (&header,body);
  146.   }
  147.   strcat (header,"\015\012");    /* write terminating blank line */
  148. }
  149.  
  150. /* Write RFC822 address from header line
  151.  * Accepts: pointer to destination string pointer
  152.  *        pointer to header type
  153.  *        message to interpret
  154.  *        address to interpret
  155.  */
  156.  
  157. void rfc822_address_line (header,type,env,adr)
  158.     char **header;
  159.     char *type;
  160.     ENVELOPE *env;
  161.     ADDRESS *adr;
  162. {
  163.   char *t,tmp[MAILTMPLEN];
  164.   long i,len,n = 0;
  165.   char *s = (*header += strlen (*header));
  166.   if (adr) {            /* do nothing if no addresses */
  167.     if (env && env->remail) strcat (s,"ReSent-");
  168.     strcat (s,type);        /* write header name */
  169.     strcat (s,": ");
  170.     s += (len = strlen (s));    /* initial string length */
  171.     do {            /* run down address list */
  172.       *(t = tmp) = '\0';    /* initially empty string */
  173.                 /* start of group? */
  174.       if (adr->mailbox && !adr->host) {
  175.                 /* yes, write group name */
  176.     rfc822_cat (t,adr->mailbox,rspecials);
  177.     strcat (t,": ");    /* write group identifier */
  178.     n++;            /* in a group, suppress expansion */
  179.       }
  180.       else {            /* not start of group */
  181.     if (!adr->host && n) {    /* end of group? */
  182.       strcat (t,";");    /* write close delimiter */
  183.       n--;            /* no longer in a group */
  184.     }
  185.     else if (!n) {        /* only print if not inside a group */
  186.                 /* simple case? */
  187.       if (!(adr->personal || adr->adl)) rfc822_address (t,adr);
  188.       else {        /* no, must use phrase <route-addr> form */
  189.         if (adr->personal) rfc822_cat (t,adr->personal,rspecials);
  190.         strcat (t," <");    /* write address delimiter */
  191.                 /* write address */
  192.         rfc822_address (t,adr);
  193.         strcat (t,">");    /* closing delimiter */
  194.       }
  195.     }
  196.                 /* write delimiter for next recipient */
  197.     if (!n && adr->next && adr->next->mailbox) strcat (t,", ");
  198.       }
  199.                 /* if string would overflow */
  200.       if ((len += (i = strlen (t))) > 78) {
  201.     len = 4 + i;        /* continue it on a new line */
  202.     *s++ = '\015'; *s++ = '\012';
  203.     *s++ = ' '; *s++ = ' '; *s++ = ' '; *s++ = ' ';
  204.       }
  205.       while (*t) *s++ = *t++;    /* write this address */
  206.     } while (adr = adr->next);
  207.                 /* tie off header line */
  208.     *s++ = '\015'; *s++ = '\012'; *s = '\0';
  209.     *header = s;        /* set return value */
  210.   }
  211. }
  212.  
  213. /* Write RFC822 text from header line
  214.  * Accepts: pointer to destination string pointer
  215.  *        pointer to header type
  216.  *        message to interpret
  217.  *        pointer to text
  218.  */
  219.  
  220. void rfc822_header_line (header,type,env,text)
  221.     char **header;
  222.     char *type;
  223.     ENVELOPE *env;
  224.     char *text;
  225. {
  226.   if (text) sprintf ((*header += strlen (*header)),"%s%s: %s\015\012",
  227.              env->remail ? "ReSent-" : "",type,text);
  228. }
  229.  
  230.  
  231. /* Write RFC822 address
  232.  * Accepts: pointer to destination string
  233.  *        address to interpret
  234.  */
  235.  
  236. void rfc822_write_address (dest,adr)
  237.     char *dest;
  238.     ADDRESS *adr;
  239. {
  240.   while (adr) {
  241.                 /* start of group? */
  242.     if (adr->mailbox && !adr->host) {
  243.                 /* yes, write group name */
  244.       rfc822_cat (dest,adr->mailbox,rspecials);
  245.       strcat (dest,": ");    /* write group identifier */
  246.       adr = adr->next;        /* move to next address block */
  247.     }
  248.     else {            /* end of group? */
  249.       if (!adr->host) strcat (dest,";");
  250.                 /* simple case? */
  251.       else if (!(adr->personal || adr->adl)) rfc822_address (dest,adr);
  252.       else {            /* no, must use phrase <route-addr> form */
  253.     if (adr->personal) {    /* in case have adl but no personal name */
  254.       rfc822_cat (dest,adr->personal,rspecials);
  255.       strcat (dest," ");
  256.     }
  257.     strcat (dest,"<");    /* write address delimiter */
  258.     rfc822_address (dest,adr);/* write address */
  259.     strcat (dest,">");    /* closing delimiter */
  260.       }
  261.                 /* delimit if there is one */
  262.       if ((adr = adr->next) && adr->mailbox) strcat (dest,", ");
  263.     }
  264.   }
  265. }
  266.  
  267. /* Write RFC822 route-address to string
  268.  * Accepts: pointer to destination string
  269.  *        address to interpret
  270.  */
  271.  
  272. void rfc822_address (dest,adr)
  273.     char *dest;
  274.     ADDRESS *adr;
  275. {
  276.   if (adr && adr->host) {    /* no-op if no address */
  277.     if (adr->adl) {        /* have an A-D-L? */
  278.       strcat (dest,adr->adl);
  279.       strcat (dest,":");
  280.     }
  281.                 /* write mailbox name */
  282.     rfc822_cat (dest,adr->mailbox,wspecials);
  283.     if (*adr->host != '@') {    /* unless null host (HIGHLY discouraged!) */
  284.       strcat (dest,"@");    /* host delimiter */
  285.       strcat (dest,adr->host);    /* write host name */
  286.     }
  287.   }
  288. }
  289.  
  290.  
  291. /* Concatenate RFC822 string
  292.  * Accepts: pointer to destination string
  293.  *        pointer to string to concatenate
  294.  *        list of special characters
  295.  */
  296.  
  297. void rfc822_cat (dest,src,specials)
  298.     char *dest;
  299.     char *src;
  300.     const char *specials;
  301. {
  302.   char *s;
  303.   if (strpbrk (src,specials)) {    /* any specials present? */
  304.     strcat (dest,"\"");        /* opening quote */
  305.                 /* truly bizarre characters in there? */
  306.     while (s = strpbrk (src,"\\\"")) {
  307.       strncat (dest,src,s-src);    /* yes, output leader */
  308.       strcat (dest,"\\");    /* quoting */
  309.       strncat (dest,s,1);    /* output the bizarre character */
  310.       src = ++s;        /* continue after the bizarre character */
  311.     }
  312.     if (*src) strcat (dest,src);/* output non-bizarre string */
  313.     strcat (dest,"\"");        /* closing quote */
  314.   }
  315.   else strcat (dest,src);    /* otherwise it's the easy case */
  316. }
  317.  
  318. /* Write body content header
  319.  * Accepts: pointer to destination string pointer
  320.  *        pointer to body to interpret
  321.  */
  322.  
  323. void rfc822_write_body_header (dst,body)
  324.     char **dst;
  325.     BODY *body;
  326. {
  327.   char *s;
  328.   PARAMETER *param = body->parameter;
  329.   sprintf (*dst += strlen (*dst),"Content-Type: %s",body_types[body->type]);
  330.   s = body->subtype ? body->subtype : rfc822_default_subtype (body->type);
  331.   sprintf (*dst += strlen (*dst),"/%s",s);
  332.   if (param) do {
  333.     sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
  334.     rfc822_cat (*dst,param->value,tspecials);
  335.   } while (param = param->next);
  336.   else if (body->type == TYPETEXT) strcat (*dst,"; CHARSET=US-ASCII");
  337.   strcpy (*dst += strlen (*dst),"\015\012");
  338.   if (body->encoding)        /* note: encoding 7BIT never output! */
  339.     sprintf (*dst += strlen (*dst),"Content-Transfer-Encoding: %s\015\012",
  340.          body_encodings[body->encoding]);
  341.   if (body->id) sprintf (*dst += strlen (*dst),"Content-ID: %s\015\012",
  342.              body->id);
  343.   if (body->description)
  344.     sprintf (*dst += strlen (*dst),"Content-Description: %s\015\012",
  345.          body->description);
  346.   if (body->md5)
  347.     sprintf (*dst += strlen (*dst),"Content-MD5: %s\015\012",body->md5);
  348. }
  349.  
  350.  
  351. /* Subtype defaulting (a no-no, but regretably necessary...)
  352.     a no-no;
  353.      but regretably necessary...;
  354.  * Accepts: type code
  355.  * Returns: default subtype name
  356.  */
  357.  
  358. char *rfc822_default_subtype (type)
  359.     unsigned short type;
  360. {
  361.   switch (type) {
  362.   case TYPETEXT:        /* default is TEXT/PLAIN */
  363.     return "PLAIN";
  364.   case TYPEMULTIPART:        /* default is MULTIPART/MIXED */
  365.     return "MIXED";
  366.   case TYPEMESSAGE:        /* default is MESSAGE/RFC822 */
  367.     return "RFC822";
  368.   case TYPEAPPLICATION:        /* default is APPLICATION/OCTET-STREAM */
  369.     return "OCTET-STREAM";
  370.   case TYPEAUDIO:        /* default is AUDIO/BASIC */
  371.     return "BASIC";
  372.   default:            /* others have no default subtype */
  373.     return "UNKNOWN";
  374.   }
  375. }
  376.  
  377. /* RFC822 parsing routines */
  378.  
  379.  
  380. /* Parse an RFC822 message
  381.  * Accepts: pointer to return envelope
  382.  *        pointer to return body
  383.  *        pointer to header
  384.  *        header byte count
  385.  *        pointer to body stringstruct
  386.  *        pointer to local host name
  387.  *        pointer to scratch buffer
  388.  */
  389.  
  390. void rfc822_parse_msg (en,bdy,s,i,bs,host,tmp)
  391.     ENVELOPE **en;
  392.     BODY **bdy;
  393.     char *s;
  394.     unsigned long i;
  395.                     STRING *bs;
  396.     char *host;
  397.     char *tmp;
  398. {
  399.   char c,*t,*d;
  400.   ENVELOPE *env = (*en = mail_newenvelope ());
  401.   BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
  402.   long MIMEp = NIL;        /* flag that MIME semantics are in effect */
  403.   long PathP = NIL;        /* flag that a Path: was seen */
  404.   while (i && *s != '\n') {    /* until end of header */
  405.     t = tmp;            /* initialize buffer pointer */
  406.     c = ' ';            /* and previous character */
  407.     while (i && c) {        /* collect text until logical end of line */
  408.       switch (c = *s++) {    /* slurp a character */
  409.       case '\015':        /* return, possible end of logical line */
  410.     if (*s == '\n') break;    /* ignore if LF follows */
  411.       case '\012':        /* LF, possible end of logical line */
  412.                 /* tie off unless next line starts with WS */
  413.     if (*s != ' ' && *s != '\t') *t++ = c = '\0';
  414.     break;
  415.       case '\t':        /* tab */
  416.     *t++ = ' ';        /* coerce to space */
  417.     break;
  418.       default:            /* all other characters */
  419.     *t++ = c;        /* insert the character into the line */
  420.     break;
  421.       }
  422.       if (!--i) *t++ = '\0';    /* see if end of header */
  423.     }
  424.  
  425.                 /* find header item type */
  426.     if (t = d = strchr (tmp,':')) {
  427.       *d++ = '\0';        /* tie off header item, point at its data */
  428.       while (*d == ' ') d++;    /* flush whitespace */
  429.       while ((tmp < t--) && (*t == ' ')) *t = '\0';
  430.       switch (*ucase (tmp)) {    /* dispatch based on first character */
  431.       case '>':            /* possible >From: */
  432.     if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
  433.     break;
  434.       case 'B':            /* possible bcc: */
  435.     if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
  436.     break;
  437.       case 'C':            /* possible cc: or Content-<mumble>*/
  438.     if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
  439.     else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
  440.          (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
  441.          (tmp[7] == '-') && body &&
  442.          (MIMEp || (search (s-1,i,"\012MIME-Version",(long) 13))))
  443.       rfc822_parse_content_header (body,tmp+8,d);
  444.     break;
  445.       case 'D':            /* possible Date: */
  446.     if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
  447.     break;
  448.       case 'F':            /* possible From: */
  449.     if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
  450.     else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
  451.       t = env->followup_to = (char *) fs_get (1 + strlen (d));
  452.       while (c = *d++) if (c != ' ') *t++ = c;
  453.       *t++ = '\0';
  454.     }
  455.     break;
  456.       case 'I':            /* possible In-Reply-To: */
  457.     if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
  458.       env->in_reply_to = cpystr (d);
  459.     break;
  460.       case 'M':            /* possible Message-ID: or MIME-Version: */
  461.     if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
  462.       env->message_id = cpystr (d);
  463.     else if (!strcmp (tmp+1,"IME-VERSION")) {
  464.                 /* tie off at end of phrase */
  465.       if (t = rfc822_parse_phrase (d)) *t = '\0';
  466.       rfc822_skipws (&d);    /* skip whitespace */
  467.                 /* known version? */
  468.       if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
  469.         mm_log ("Warning: message has unknown MIME version",PARSE);
  470.       MIMEp = T;        /* note that we are MIME */
  471.     }
  472.     break;
  473.       case 'N':            /* possible Newsgroups: */
  474.     if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
  475.       t = env->newsgroups = (char *) fs_get (1 + strlen (d));
  476.       while (c = *d++) if (c != ' ') *t++ = c;
  477.       *t++ = '\0';
  478.     }
  479.     break;
  480.  
  481.       case 'P':            /* possible Path: */
  482.     if (!strcmp (tmp+1,"ATH")) PathP = T;
  483.     break;
  484.       case 'R':            /* possible Reply-To: */
  485.     if (!strcmp (tmp+1,"EPLY-TO"))
  486.       rfc822_parse_adrlist (&env->reply_to,d,host);
  487.     else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
  488.       env->references = cpystr (d);
  489.     break;
  490.       case 'S':            /* possible Subject: or Sender: */
  491.     if (!env->subject && !strcmp (tmp+1,"UBJECT"))
  492.       env->subject = cpystr (d);
  493.     else if (!strcmp (tmp+1,"ENDER"))
  494.       rfc822_parse_adrlist (&env->sender,d,host);
  495.     break;
  496.       case 'T':            /* possible To: */
  497.     if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
  498.     break;
  499.       default:
  500.     break;
  501.       }
  502.     }
  503.   }
  504.   /* We require a Path: header and/or a Message-ID belonging to a known
  505.    * winning mail program, in order to believe Newsgroups:.  This is because
  506.    * of the unfortunate existance of certain cretins who believe that it
  507.    * is reasonable to transmit messages via SMTP with a "Newsgroups" header
  508.    * that were not actually posted to any of the named newsgroups.
  509.    * The authors of other high-quality email/news software are encouraged to
  510.    * use similar methods to indentify messages as coming from their software,
  511.    * and having done so, to tell us so they too can be blessed in this list.
  512.    */
  513.   if (env->newsgroups && !PathP && env->message_id &&
  514.       strncmp (env->message_id,"<Pine.",6) &&
  515.       strncmp (env->message_id,"<MS-C.",6) &&
  516.       strncmp (env->message_id,"<MailManager.",13) &&
  517.       strncmp (env->message_id,"<EasyMail.",11) &&
  518.       strncmp (env->message_id,"<ML-",4)) {
  519.     sprintf (tmp,"Probable bogus newsgroup list \"%s\" in \"%s\" ignored",
  520.          env->newsgroups,env->message_id);
  521.     mm_log (tmp,PARSE);
  522.     fs_give ((void **) &env->newsgroups);
  523.   }
  524.                 /* default Sender: and Reply-To: to From: */
  525.   if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
  526.   if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
  527.                 /* now parse the body */
  528.   if (body) rfc822_parse_content (body,bs,host,tmp);
  529. }
  530.  
  531. /* Parse a message body content
  532.  * Accepts: pointer to body structure
  533.  *        body string
  534.  *        pointer to local host name
  535.  *        pointer to scratch buffer
  536.  */
  537.  
  538. void rfc822_parse_content (body,bs,h,t)
  539.     BODY *body;
  540.     STRING *bs;
  541.     char *h;
  542.     char *t;
  543. {
  544.   char c,c1,*s,*s1;
  545.   int f;
  546.   unsigned long pos = GETPOS (bs);
  547.   unsigned long i = SIZE (bs);
  548.   unsigned long j,k,m = 0;
  549.   PARAMETER *param;
  550.   PART *part = NIL;
  551.   body->size.ibytes = i;    /* note body size in all cases */
  552.   body->size.bytes = ((body->encoding == ENCBINARY) ||
  553.               (body->type == TYPEMULTIPART)) ? i : strcrlflen (bs);
  554.   switch (body->type) {        /* see if anything else special to do */
  555.   case TYPETEXT:        /* text content */
  556.     if (!body->subtype)        /* default subtype */
  557.       body->subtype = cpystr (rfc822_default_subtype (body->type));
  558.     if (!body->parameter) {    /* default parameters */
  559.       body->parameter = mail_newbody_parameter ();
  560.       body->parameter->attribute = cpystr ("CHARSET");
  561.       body->parameter->value = cpystr ("US-ASCII");
  562.     }
  563.                 /* count number of lines */
  564.     while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
  565.     break;
  566.  
  567.   case TYPEMESSAGE:        /* encapsulated message */
  568.     body->contents.msg.env = NIL;
  569.     body->contents.msg.body = NIL;
  570.     body->contents.msg.text = NIL;
  571.     body->contents.msg.offset = pos;
  572.                 /* encapsulated RFC-822 message? */
  573.     if (!strcmp (body->subtype,"RFC822")) {
  574.       if ((body->encoding == ENCBASE64) ||
  575.       (body->encoding == ENCQUOTEDPRINTABLE)
  576.       || (body->encoding == ENCOTHER))
  577.     mm_log ("Ignoring nested encoding of message contents",PARSE);
  578.                 /* hunt for blank line */
  579.       for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
  580.        j++) if ((c1 = SNX (bs)) != '\015') c = c1;
  581.       if (i > j) c1 = SNX (bs);    /* unless no more text, body starts here */
  582.                 /* note body text offset and header size */
  583.       j = (body->contents.msg.offset = GETPOS (bs)) - pos;
  584.       SETPOS (bs,pos);        /* copy header string */
  585.       s = (char *) fs_get ((size_t) j + 1);
  586.       for (s1 = s,k = j; k--; *s1++ = SNX (bs));
  587.       s[j] = '\0';        /* tie off string (not really necessary) */
  588.                 /* now parse the body */
  589.       rfc822_parse_msg (&body->contents.msg.env,&body->contents.msg.body,s,j,
  590.             bs,h,t);
  591.       fs_give ((void **) &s);    /* free header string */
  592.       SETPOS (bs,pos);        /* restore position */
  593.     }
  594.                 /* count number of lines */
  595.     while (i--) if (SNX (bs) == '\n') body->size.lines++;
  596.     break;
  597.  
  598.   case TYPEMULTIPART:        /* multiple parts */
  599.                 /* remember if digest */
  600.     f = !strcmp (body->subtype,"DIGEST");
  601.     if ((body->encoding == ENCBASE64) || (body->encoding == ENCQUOTEDPRINTABLE)
  602.     || (body->encoding == ENCOTHER))
  603.       mm_log ("Ignoring nested encoding of multipart contents",PARSE);
  604.                 /* find cookie */
  605.     for (*t = '\0',param = body->parameter; param && !*t; param = param->next)
  606.       if (!strcmp (param->attribute,"BOUNDARY")) strcpy (t,param->value);
  607.     if (!*t) strcpy (t,"-");    /* yucky default */
  608.     j = strlen (t);        /* length of cookie and header */
  609.     c = '\012';            /* initially at beginning of line */
  610.     while (i > j) {        /* examine data */
  611.       m = GETPOS (bs);        /* note position */
  612.       if (m) m--;        /* get position in front of character */
  613.       switch (c) {        /* examine each line */
  614.       case '\015':        /* handle CRLF form */
  615.     if (CHR (bs) == '\012'){/* following LF? */
  616.       c = SNX (bs); i--;    /* yes, slurp it */
  617.     }
  618.       case '\012':        /* at start of a line, start with -- ? */
  619.     if (i-- && ((c = SNX (bs)) == '-') && i-- && ((c = SNX (bs)) == '-')) {
  620.                 /* see if cookie matches */
  621.       for (k = j,s = t; i-- && *s++ == (c = SNX (bs)) && --k;);
  622.       if (k) break;        /* strings didn't match if non-zero */
  623.                 /* look at what follows cookie */
  624.       if (i && i--) switch (c = SNX (bs)) {
  625.       case '-':        /* at end if two dashes */
  626.         if ((i && i--) && ((c = SNX (bs)) == '-') &&
  627.         ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) {
  628.                 /* if have a final part calculate its size */
  629.           if (part) part->body.size.bytes = (m > part->offset) ?
  630.         (m - part->offset) : 0;
  631.           part = NIL; i = 1; /* terminate scan */
  632.         }
  633.         break;
  634.       case '\015':        /* handle CRLF form */
  635.         if (i && CHR (bs) == '\012') {
  636.           c = SNX (bs); i--;/* yes, slurp it */
  637.         }
  638.       case '\012':        /* new line */
  639.         if (part) {        /* calculate size of previous */
  640.           part->body.size.bytes = (m>part->offset) ? (m-part->offset) : 0;
  641.                 /* instantiate next */
  642.           part = part->next = mail_newbody_part ();
  643.         }            /* otherwise start new list */
  644.         else part = body->contents.part = mail_newbody_part ();
  645.                 /* digest has a different default */
  646.         if (f) part->body.subtype =
  647.           cpystr (rfc822_default_subtype (part->body.type = TYPEMESSAGE));
  648.                 /* note offset from main body */
  649.         part->offset = GETPOS (bs);
  650.         break;
  651.       default:        /* whatever it was it wasn't valid */
  652.         break;
  653.       }
  654.     }
  655.     break;
  656.       default:            /* not at a line */
  657.     c = SNX (bs); i--;    /* get next character */
  658.     break;
  659.       }                /* calculate size of any final part */
  660.     }
  661.     if (part) part->body.size.bytes = i + ((GETPOS (bs) > part->offset) ?
  662.                        (GETPOS (bs) - part->offset) : 0);
  663.  
  664.                 /* parse body parts */
  665.     for (part = body->contents.part; part; part = part->next) {
  666.       SETPOS (bs,part->offset);    /* move to that part of the body */
  667.                 /* get size of this part, ignore if empty */
  668.       if (i = part->body.size.bytes) {
  669.                 /* until end of header */
  670.     while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) {
  671.       s1 = t;        /* initialize buffer pointer */
  672.       c = ' ';        /* and previous character */
  673.       while (c) {        /* collect text until logical end of line */
  674.         switch (c1 = SNX (bs)) {
  675.         case '\015':    /* return */
  676.           if (i && (CHR (bs) == '\012')) {
  677.         c1 = SNX (bs);    /* eat any LF following */
  678.         i--;
  679.           }
  680.         case '\012':    /* newline, possible end of logical line */
  681.           if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
  682.         *s1 = c = '\0';    /* tie off unless continuation */
  683.           break;
  684.         case '\t':        /* tab */
  685.         case ' ':        /* insert whitespace if not already there */
  686.           if (c != ' ') *s1++ = c = ' ';
  687.           break;
  688.         default:        /* all other characters */
  689.           *s1++ = c = c1;    /* insert the character into the line */
  690.           break;
  691.         }
  692.                 /* end of data ties off the header */
  693.         if (!--i) *s1++ = c = '\0';
  694.       }
  695.                 /* find header item type */
  696.       if (s = strchr (t,':')) {
  697.         *s++ = '\0';    /* tie off header item, point at its data */
  698.                 /* flush whitespace */
  699.         while (*s == ' ') s++;
  700.         if (s1 = strchr (ucase (t),' ')) *s1 = '\0';
  701.         if ((t[0] == 'C') && (t[1] == 'O') && (t[2] == 'N') &&
  702.         (t[3] == 'T') && (t[4] == 'E') && (t[5] == 'N') &&
  703.         (t[6] == 'T') && (t[7] == '-'))
  704.           rfc822_parse_content_header (&part->body,t+8,s);
  705.       }
  706.     }            /* skip header trailing (CR)LF */
  707.     if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
  708.     if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
  709.     j = bs->size;        /* save upper level size */
  710.       }
  711.                 /* set offset for next level, fake size to i */
  712.       bs->size = (part->offset = GETPOS (bs)) + i;
  713.                 /* now parse it */
  714.       rfc822_parse_content (&part->body,bs,h,t);
  715.       bs->size = j;        /* restore current level size */
  716.     }
  717.     break;
  718.   default:            /* nothing special to do in any other case */
  719.     break;
  720.   }
  721. }
  722.  
  723. /* Parse RFC822 body content header
  724.  * Accepts: body to write to
  725.  *        possible content name
  726.  *        remainder of header
  727.  */
  728.  
  729. void rfc822_parse_content_header (body,name,s)
  730.     BODY *body;
  731.     char *name;
  732.     char *s;
  733. {
  734.   PARAMETER *param = NIL;
  735.   char tmp[MAILTMPLEN];
  736.   char c,*t;
  737.   long i;
  738.   switch (*name) {
  739.   case 'I':            /* possible Content-ID */
  740.     if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
  741.     break;
  742.   case 'D':            /* possible Content-Description */
  743.     if (!(strcmp (name+1,"ESCRIPTION") || body->description))
  744.       body->description = cpystr (s);
  745.     break;
  746.   case 'M':            /* possible Content-MD5 */
  747.     if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
  748.     break;
  749.   case 'T':            /* possible Content-Type/Transfer-Encoding */
  750.     if (!(strcmp (name+1,"YPE") || body->type || body->subtype ||
  751.       body->parameter)) {
  752.                 /* get type word */
  753.       if (!(name = rfc822_parse_word (s,ptspecials))) break;
  754.       c = *name;        /* remember delimiter */
  755.       *name = '\0';        /* tie off type */
  756.       ucase (s);        /* search for body type */
  757.       for (i=0; (i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]); i++);
  758.       if (i > TYPEMAX) body->type = TYPEOTHER;
  759.       else {            /* if empty slot, assign it to this type */
  760.     if (!body_types[i]) body_types[i] = cpystr (s);
  761.     body->type = (unsigned short) i;
  762.       }
  763.       *name = c;        /* restore delimiter */
  764.       rfc822_skipws (&name);    /* skip whitespace */
  765.       if ((*name == '/') &&    /* subtype? */
  766.       (name = rfc822_parse_word ((s = ++name),ptspecials))) {
  767.     c = *name;        /* save delimiter */
  768.     *name = '\0';        /* tie off subtype */
  769.     rfc822_skipws (&s);    /* copy subtype */
  770.     body->subtype = ucase (cpystr (s ? s :
  771.                        rfc822_default_subtype (body->type)));
  772.     *name = c;        /* restore delimiter */
  773.     rfc822_skipws (&name);    /* skip whitespace */
  774.       }
  775.                 /* subtype defaulting is a no-no, but... */
  776.       else {
  777.     body->subtype = cpystr (rfc822_default_subtype (body->type));
  778.     if (!name) {        /* did the fool have a subtype delimiter? */
  779.       name = s;        /* barf, restore pointer */
  780.       rfc822_skipws (&name);/* skip leading whitespace */
  781.     }
  782.       }
  783.  
  784.                 /* parameter list? */
  785.       while (name && (*name == ';') &&
  786.          (name = rfc822_parse_word ((s = ++name),ptspecials))) {
  787.     c = *name;        /* remember delimiter */
  788.     *name = '\0';        /* tie off attribute name */
  789.     rfc822_skipws (&s);    /* skip leading attribute whitespace */
  790.     if (!*s) *name = c;    /* must have an attribute name */
  791.     else {            /* instantiate a new parameter */
  792.       if (body->parameter) param = param->next = mail_newbody_parameter ();
  793.       else param = body->parameter = mail_newbody_parameter ();
  794.       param->attribute = ucase (cpystr (s));
  795.       *name = c;        /* restore delimiter */
  796.       rfc822_skipws (&name);/* skip whitespace before equal sign */
  797.       if ((*name != '=') ||    /* missing value is a no-no too */
  798.           !(name = rfc822_parse_word ((s = ++name),ptspecials)))
  799.         param->value = cpystr ("UNKNOWN_PARAMETER_VALUE");
  800.       else {        /* good, have equals sign */
  801.         c = *name;        /* remember delimiter */
  802.         *name = '\0';    /* tie off value */
  803.         rfc822_skipws (&s);    /* skip leading value whitespace */
  804.         if (*s) param->value = rfc822_cpy (s);
  805.         *name = c;        /* restore delimiter */
  806.         rfc822_skipws (&name);
  807.       }
  808.     }
  809.       }
  810.       if (!name) {        /* must be end of poop */
  811.     if (param && param->attribute)
  812.       sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
  813.     else strcpy (tmp,"Missing parameter");
  814.     mm_log (tmp,PARSE);
  815.       }
  816.       else if (*name) {        /* must be end of poop */
  817.     sprintf (tmp,"Unexpected characters at end of parameters: %.80s",name);
  818.     mm_log (tmp,PARSE);
  819.       }
  820.     }
  821.     else if (!strcmp (name+1,"RANSFER-ENCODING")) {
  822.                 /* flush out any confusing whitespace */
  823.       if (t = strchr (ucase (s),' ')) *t = '\0';
  824.                 /* search for body encoding */
  825.       for (i = 0; (i <= ENCMAX) && body_encodings[i] &&
  826.        strcmp (s,body_encodings[i]); i++);
  827.       if (i > ENCMAX) body->type = ENCOTHER;
  828.       else {            /* if empty slot, assign it to this type */
  829.     if (!body_encodings[i]) body_encodings[i] = cpystr (s);
  830.     body->encoding = (unsigned short) i;
  831.       }
  832.     }
  833.     break;
  834.   default:            /* otherwise unknown */
  835.     break;
  836.   }
  837. }
  838.  
  839. /* Parse RFC822 address list
  840.  * Accepts: address list to write to
  841.  *        input string
  842.  *        default host name
  843.  */
  844.  
  845. void rfc822_parse_adrlist (lst,string,host)
  846.     ADDRESS **lst;
  847.     char *string;
  848.     char *host;
  849. {
  850.   char c,*s,tmp[MAILTMPLEN];
  851.   ADDRESS *last = *lst;
  852.   ADDRESS *adr;
  853.   if (!string) return;        /* no string */
  854.   rfc822_skipws (&string);    /* skip leading WS */
  855.   if (!*string) return;        /* empty string */
  856.                 /* run to tail of list */
  857.   if (last) while (last->next) last = last->next;
  858.   while (string) {        /* loop until string exhausted */
  859.                 /* got an address? */
  860.     if (adr = rfc822_parse_address (lst,last,&string,host)) {
  861.       last = adr;
  862.                 /* analyze what follows */
  863.       if (string) switch (c = *string) {
  864.       case ',':            /* comma? */
  865.     ++string;        /* then another address follows */
  866.     break;
  867.       default:
  868.     s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
  869.       "Unexpected characters at end of address: %.80s";
  870.     sprintf (tmp,s,string);
  871.     mm_log (tmp,PARSE);
  872.     last = last->next = mail_newaddr ();
  873.     last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
  874.     last->host = cpystr (errhst);
  875.                 /* falls through */
  876.       case '\0':        /* null-specified address? */
  877.     string = NIL;        /* punt remainder of parse */
  878.     break;
  879.       }
  880.     }
  881.     else if (string) {        /* bad mailbox */
  882.       rfc822_skipws (&string);    /* skip WS */
  883.       if (!*string) strcpy (tmp,"Missing address after comma");
  884.       else sprintf (tmp,"Invalid mailbox list: %.80s",string);
  885.       mm_log (tmp,PARSE);
  886.       string = NIL;
  887.       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
  888.       adr->host = cpystr (errhst);
  889.       if (last) last = last->next = adr;
  890.       else *lst = last = adr;
  891.       break;
  892.     }
  893.   }
  894. }
  895.  
  896. /* Parse RFC822 address
  897.  * Accepts: address list to write to
  898.  *        tail of address list
  899.  *        pointer to input string
  900.  *        default host name
  901.  */
  902.  
  903. ADDRESS *rfc822_parse_address (lst,last,string,defaulthost)
  904.     ADDRESS **lst;
  905.     ADDRESS *last;
  906.     char **string;
  907.                         char *defaulthost;
  908. {
  909.   ADDRESS *adr;
  910.   if (!*string) return NIL;    /* no string */
  911.   rfc822_skipws (string);    /* skip leading WS */
  912.   if (!**string) return NIL;    /* empty string */
  913.   if (adr = rfc822_parse_group (lst,last,string,defaulthost)) last = adr;
  914.                 /* got an address? */
  915.   else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
  916.     if (!*lst) *lst = adr;    /* yes, first time through? */
  917.     else last->next = adr;    /* no, append to the list */
  918.     last = adr;            /* set for subsequent linking */
  919.   }
  920.   else if (*string) return NIL;
  921.   return last;
  922. }
  923.  
  924. /* Parse RFC822 group
  925.  * Accepts: address list to write to
  926.  *        pointer to tail of address list
  927.  *        pointer to input string
  928.  *        default host name
  929.  */
  930.  
  931. ADDRESS *rfc822_parse_group (lst,last,string,defaulthost)
  932.     ADDRESS **lst;
  933.     ADDRESS *last;
  934.     char **string;
  935.                       char *defaulthost;
  936. {
  937.   char tmp[MAILTMPLEN];
  938.   char *p,*s;
  939.   ADDRESS *adr;
  940.   if (!*string) return NIL;    /* no string */
  941.   rfc822_skipws (string);    /* skip leading WS */
  942.   if (!**string ||        /* trailing whitespace or not group */
  943.       ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
  944.     return NIL;
  945.   s = p;            /* end of candidate phrase */
  946.   rfc822_skipws (&s);        /* find delimiter */
  947.   if (*s != ':') return NIL;    /* not really a group */
  948.   *p = '\0';            /* tie off group name */
  949.   p = ++s;            /* continue after the delimiter */
  950.   rfc822_skipws (&p);        /* skip subsequent whitespace */
  951.                 /* write as address */
  952.   (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
  953.   if (!*lst) *lst = adr;    /* first time through? */
  954.   else last->next = adr;    /* no, append to the list */
  955.   last = adr;            /* set for subsequent linking */
  956.   *string = p;            /* continue after this point */
  957.   while (*string && **string && (**string != ';')) {
  958.     if (adr = rfc822_parse_address (lst,last,string,defaulthost)) {
  959.       last = adr;
  960.       if (*string) {        /* anything more? */
  961.     rfc822_skipws (string);    /* skip whitespace */
  962.     switch (**string) {    /* see what follows */
  963.     case ',':        /* another address? */
  964.       ++*string;        /* yes, skip past the comma */
  965.     case ';':        /* end of group? */
  966.     case '\0':        /* end of string */
  967.       break;
  968.     default:
  969.       sprintf (tmp,"Unexpected characters after address in group: %.80s",
  970.            *string);
  971.       mm_log (tmp,PARSE);
  972.       *string = NIL;    /* cancel remainder of parse */
  973.       last = last->next = mail_newaddr ();
  974.       last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
  975.       last->host = cpystr (errhst);
  976.     }
  977.       }
  978.     }
  979.     else {            /* bogon */
  980.       sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
  981.       mm_log (tmp,PARSE);
  982.       *string = NIL;        /* cancel remainder of parse */
  983.       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
  984.       adr->host = cpystr (errhst);
  985.       last = last->next = adr;
  986.     }
  987.   }
  988.                 /* skip close delimiter */
  989.   if (*string && (**string == ';')) ++*string;
  990.                 /* append end of address mark to the list */
  991.   last->next = (adr = mail_newaddr ());
  992.   last = adr;            /* set for subsequent linking */
  993.   return last;            /* return the tail */
  994. }
  995.  
  996. /* Parse RFC822 mailbox
  997.  * Accepts: pointer to string pointer
  998.  *        default host
  999.  * Returns: address
  1000.  *
  1001.  * Updates string pointer
  1002.  */
  1003.  
  1004. ADDRESS *rfc822_parse_mailbox (string,defaulthost)
  1005.     char **string;
  1006.     char *defaulthost;
  1007. {
  1008.   ADDRESS *adr;
  1009.   char *s,*phrase;
  1010.   if (!*string) return NIL;    /* no string */
  1011.   rfc822_skipws (string);    /* flush leading whitespace */
  1012.   if (!**string) return NIL;    /* empty string */
  1013.  
  1014.   /* This is much more complicated than it should be because users like
  1015.    * to write local addrspecs without "@localhost".  This makes it very
  1016.    * difficult to tell a phrase from an addrspec!
  1017.    * The other problem we must cope with is a route-addr without a leading
  1018.    * phrase.  Yuck!
  1019.    */
  1020.  
  1021.   if (*(s = *string) == '<')     /* note start, handle case of phraseless RA */
  1022.     adr = rfc822_parse_routeaddr (s,string,defaulthost);
  1023.   else {            /* get phrase if any */
  1024.     if ((phrase = rfc822_parse_phrase (s)) &&
  1025.     (adr = rfc822_parse_routeaddr (phrase,string,defaulthost))) {
  1026.       *phrase = '\0';        /* tie off phrase */
  1027.                 /* phrase is a personal name */
  1028.       adr->personal = rfc822_cpy (s);
  1029.     }
  1030.     else adr = rfc822_parse_addrspec (s,string,defaulthost);
  1031.   }
  1032.   return adr;            /* return the address */
  1033. }
  1034.  
  1035. /* Parse RFC822 route-address
  1036.  * Accepts: string pointer
  1037.  *        pointer to string pointer to update
  1038.  * Returns: address
  1039.  *
  1040.  * Updates string pointer
  1041.  */
  1042.  
  1043. ADDRESS *rfc822_parse_routeaddr (string,ret,defaulthost)
  1044.     char *string;
  1045.     char **ret;
  1046.     char *defaulthost;
  1047. {
  1048.   char tmp[MAILTMPLEN];
  1049.   ADDRESS *adr;
  1050.   char *adl = NIL;
  1051.   char *routeend = NIL;
  1052.   if (!string) return NIL;
  1053.   rfc822_skipws (&string);    /* flush leading whitespace */
  1054.                 /* must start with open broket */
  1055.   if (*string != '<') return NIL;
  1056.   if (string[1] == '@') {    /* have an A-D-L? */
  1057.     adl = ++string;        /* yes, remember that fact */
  1058.     while (*string != ':') {    /* search for end of A-D-L */
  1059.                 /* punt if never found */
  1060.       if (!*string) return NIL;
  1061.       ++string;            /* try next character */
  1062.     }
  1063.     *string = '\0';        /* tie off A-D-L */
  1064.     routeend = string;        /* remember in case need to put back */
  1065.   }
  1066.                 /* parse address spec */
  1067.   if (!(adr = rfc822_parse_addrspec (++string,ret,defaulthost))) {
  1068.     if (adl) *routeend = ':';    /* put colon back since parse barfed */
  1069.     return NIL;
  1070.   }
  1071.                 /* have an A-D-L? */
  1072.   if (adl) adr->adl = cpystr (adl);
  1073.   if (*ret) if (**ret == '>') {    /* make sure terminated OK */
  1074.     ++*ret;            /* skip past the broket */
  1075.     rfc822_skipws (ret);    /* flush trailing WS */
  1076.     if (!**ret) *ret = NIL;    /* wipe pointer if at end of string */
  1077.     return adr;            /* return the address */
  1078.   }
  1079.   sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
  1080.        *adr->host == '@' ? "<null>" : adr->host);
  1081.   mm_log (tmp,PARSE);
  1082.   adr->next = mail_newaddr ();
  1083.   adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
  1084.   adr->next->host = cpystr (errhst);
  1085.   return adr;            /* return the address */
  1086. }
  1087.  
  1088. /* Parse RFC822 address-spec
  1089.  * Accepts: string pointer
  1090.  *        pointer to string pointer to update
  1091.  *        default host
  1092.  * Returns: address
  1093.  *
  1094.  * Updates string pointer
  1095.  */
  1096.  
  1097. ADDRESS *rfc822_parse_addrspec (string,ret,defaulthost)
  1098.     char *string;
  1099.     char **ret;
  1100.     char *defaulthost;
  1101. {
  1102.   ADDRESS *adr;
  1103.   char *end;
  1104.   char c,*s,*t;
  1105.   if (!string) return NIL;    /* no string */
  1106.   rfc822_skipws (&string);    /* flush leading whitespace */
  1107.   if (!*string) return NIL;    /* empty string */
  1108.                 /* find end of mailbox */
  1109.   if (!(end = rfc822_parse_word (string,NIL))) return NIL;
  1110.   adr = mail_newaddr ();    /* create address block */
  1111.   c = *end;            /* remember delimiter */
  1112.   *end = '\0';            /* tie off mailbox */
  1113.                 /* copy mailbox */
  1114.   adr->mailbox = rfc822_cpy (string);
  1115.   *end = c;            /* restore delimiter */
  1116.   t = end;            /* remember end of mailbox for no host case */
  1117.   rfc822_skipws (&end);        /* skip whitespace */
  1118.   if (*end == '@') {        /* have host name? */
  1119.     ++end;            /* skip delimiter */
  1120.     rfc822_skipws (&end);    /* skip whitespace */
  1121.     *ret = end;            /* update return pointer */
  1122.                     /* search for end of host */
  1123.     if (end = rfc822_parse_word ((string = end),hspecials)) {
  1124.       c = *end;            /* remember delimiter */
  1125.       *end = '\0';        /* tie off host */
  1126.                 /* copy host */
  1127.       adr->host = rfc822_cpy (string);
  1128.       *end = c;            /* restore delimiter */
  1129.     }
  1130.     else {
  1131.       mm_log ("Missing or invalid host name after @",PARSE);
  1132.       adr->host = cpystr (BADHOST);
  1133.     }
  1134.   }
  1135.   else end = t;            /* make person name default start after mbx */
  1136.                 /* default host if missing */
  1137.   if (!adr->host) adr->host = cpystr (defaulthost);
  1138.   if (end && !adr->personal) {    /* try person name in comments if missing */
  1139.     while (*end == ' ') ++end;    /* see if we can find a person name here */
  1140.     if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
  1141.       adr->personal = rfc822_cpy (s);
  1142.     rfc822_skipws (&end);    /* skip any other WS in the normal way */
  1143.   }
  1144.                 /* set return to end pointer */
  1145.   *ret = (end && *end) ? end : NIL;
  1146.   return adr;            /* return the address we got */
  1147. }
  1148.  
  1149. /* Parse RFC822 phrase
  1150.  * Accepts: string pointer
  1151.  * Returns: pointer to end of phrase
  1152.  */
  1153.  
  1154. char *rfc822_parse_phrase (s)
  1155.     char *s;
  1156. {
  1157.   char *curpos;
  1158.   if (!s) return NIL;        /* no-op if no string */
  1159.                 /* find first word of phrase */
  1160.   curpos = rfc822_parse_word (s,NIL);
  1161.   if (!curpos) return NIL;    /* no words means no phrase */
  1162.   if (!*curpos) return curpos;    /* check if string ends with word */
  1163.   s = curpos;            /* sniff past the end of this word and WS */
  1164.   rfc822_skipws (&s);        /* skip whitespace */
  1165.                 /* recurse to see if any more */
  1166.   return (s = rfc822_parse_phrase (s)) ? s : curpos;
  1167. }
  1168.  
  1169. /* Parse RFC822 word
  1170.  * Accepts: string pointer
  1171.  * Returns: pointer to end of word
  1172.  */
  1173.  
  1174. char *rfc822_parse_word (s,delimiters)
  1175.     char *s;
  1176.     const char *delimiters;
  1177. {
  1178.   char *st,*str;
  1179.   if (!s) return NIL;        /* no string */
  1180.   rfc822_skipws (&s);        /* flush leading whitespace */
  1181.   if (!*s) return NIL;        /* empty string */
  1182.                 /* default delimiters to standard */
  1183.   if (!delimiters) delimiters = wspecials;
  1184.   str = s;            /* hunt pointer for strpbrk */
  1185.   while (T) {            /* look for delimiter */
  1186.     if (!(st = strpbrk (str,delimiters))) {
  1187.       while (*s) ++s;        /* no delimiter, hunt for end */
  1188.       return s;            /* return it */
  1189.     }
  1190.     switch (*st) {        /* dispatch based on delimiter */
  1191.     case '"':            /* quoted string */
  1192.                 /* look for close quote */
  1193.       while (*++st != '"') switch (*st) {
  1194.       case '\0':        /* unbalanced quoted string */
  1195.     return NIL;        /* sick sick sick */
  1196.       case '\\':        /* quoted character */
  1197.     if (!*++st) return NIL;    /* skip the next character */
  1198.       default:            /* ordinary character */
  1199.     break;            /* no special action */
  1200.       }
  1201.       str = ++st;        /* continue parse */
  1202.       break;
  1203.     case '\\':            /* quoted character */
  1204.       /* This is wrong; a quoted-pair can not be part of a word.  However,
  1205.        * domain-literal is parsed as a word and quoted-pairs can be used
  1206.        * *there*.  Either way, it's pretty pathological.
  1207.        */
  1208.       if (st[1]) {        /* not on NUL though... */
  1209.     str = st + 2;        /* skip quoted character and go on */
  1210.     break;
  1211.       }
  1212.     default:            /* found a word delimiter */
  1213.       return (st == s) ? NIL : st;
  1214.     }
  1215.   }
  1216. }
  1217.  
  1218. /* Copy an RFC822 format string
  1219.  * Accepts: string
  1220.  * Returns: copy of string
  1221.  */
  1222.  
  1223. char *rfc822_cpy (src)
  1224.     char *src;
  1225. {
  1226.                 /* copy and unquote */
  1227.   return rfc822_quote (cpystr (src));
  1228. }
  1229.  
  1230.  
  1231. /* Unquote an RFC822 format string
  1232.  * Accepts: string
  1233.  * Returns: string
  1234.  */
  1235.  
  1236. char *rfc822_quote (src)
  1237.     char *src;
  1238. {
  1239.   char *ret = src;
  1240.   if (strpbrk (src,"\\\"")) {    /* any quoting in string? */
  1241.     char *dst = ret;
  1242.     while (*src) {        /* copy string */
  1243.       if (*src == '\"') src++;    /* skip double quote entirely */
  1244.       else {
  1245.     if (*src == '\\') src++;/* skip over single quote, copy next always */
  1246.     *dst++ = *src++;    /* copy character */
  1247.       }
  1248.     }
  1249.     *dst = '\0';        /* tie off string */
  1250.   }
  1251.   return ret;            /* return our string */
  1252. }
  1253.  
  1254.  
  1255. /* Copy address list
  1256.  * Accepts: address list
  1257.  * Returns: address list
  1258.  */
  1259.  
  1260. ADDRESS *rfc822_cpy_adr (adr)
  1261.     ADDRESS *adr;
  1262. {
  1263.   ADDRESS *dadr;
  1264.   ADDRESS *ret = NIL;
  1265.   ADDRESS *prev = NIL;
  1266.   while (adr) {            /* loop while there's still an MAP adr */
  1267.     dadr = mail_newaddr ();    /* instantiate a new address */
  1268.     if (!ret) ret = dadr;    /* note return */
  1269.     if (prev) prev->next = dadr;/* tie on to the end of any previous */
  1270.     dadr->personal = cpystr (adr->personal);
  1271.     dadr->adl = cpystr (adr->adl);
  1272.     dadr->mailbox = cpystr (adr->mailbox);
  1273.     dadr->host = cpystr (adr->host);
  1274.     prev = dadr;        /* this is now the previous */
  1275.     adr = adr->next;        /* go to next address in list */
  1276.   }
  1277.   return (ret);            /* return the MTP address list */
  1278. }
  1279.  
  1280. /* Skips RFC822 whitespace
  1281.  * Accepts: pointer to string pointer
  1282.  */
  1283.  
  1284. void rfc822_skipws (s)
  1285.     char **s;
  1286. {
  1287.   while (T) {
  1288.     if (**s == ' ') ++*s;    /* skip space */
  1289.     else if ((**s != '(') || !rfc822_skip_comment (s,(long) NIL)) return;
  1290.   }
  1291. }
  1292.  
  1293.  
  1294. /* Skips RFC822 comment
  1295.  * Accepts: pointer to string pointer
  1296.  *        trim flag
  1297.  * Returns: pointer to first non-blank character of comment
  1298.  */
  1299.  
  1300. char *rfc822_skip_comment (s,trim)
  1301.     char **s;
  1302.     long trim;
  1303. {
  1304.   char *ret,tmp[MAILTMPLEN];
  1305.   char *s1 = *s;
  1306.   char *t = NIL;
  1307.                 /* skip past whitespace */
  1308.   for (ret = ++s1; *ret == ' '; ret++);
  1309.   do switch (*s1) {        /* get character of comment */
  1310.   case '(':            /* nested comment? */
  1311.     if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
  1312.     t = --s1;            /* last significant char at end of comment */
  1313.     break;
  1314.   case ')':            /* end of comment? */
  1315.     *s = ++s1;            /* skip past end of comment */
  1316.     if (trim) {            /* if level 0, must trim */
  1317.       if (t) t[1] = '\0';    /* tie off comment string */
  1318.       else *ret = '\0';        /* empty comment */
  1319.     }
  1320.     return ret;
  1321.   case '\\':            /* quote next character? */
  1322.     if (*++s1) break;        /* drop in if null seen */
  1323.   case '\0':            /* end of string */
  1324.     sprintf (tmp,"Unterminated comment: %.80s",*s);
  1325.     mm_log (tmp,PARSE);
  1326.     **s = '\0';            /* nuke duplicate messages in case reparse */
  1327.     return NIL;            /* this is wierd if it happens */
  1328.   case ' ':            /* whitespace isn't significant */
  1329.     break;
  1330.   default:            /* random character */
  1331.     t = s1;            /* update last significant character pointer */
  1332.     break;
  1333.   } while (s1++);
  1334.   return NIL;            /* impossible, but pacify lint et al */
  1335. }
  1336.  
  1337. /* Body contents utility and encoding/decoding routines */
  1338.  
  1339.  
  1340. /* Return body contents in normal form
  1341.  * Accepts: pointer to destination
  1342.  *        pointer to length of destination
  1343.  *        returned destination length
  1344.  *        source
  1345.  *        length of source
  1346.  *        source encoding
  1347.  * Returns: destination
  1348.  *
  1349.  * Originally, this routine was supposed to do decoding as well, but that was
  1350.  * moved to a higher level.  Now, it's merely a jacket into strcrlfcpy that
  1351.  * avoids the work for BINARY segments.
  1352.  */
  1353.  
  1354. char *rfc822_contents (dst,dstl,len,src,srcl,encoding)
  1355.     char **dst;
  1356.     unsigned long *dstl;
  1357.     unsigned long *len;
  1358.                     char *src;
  1359.     unsigned long srcl;
  1360.     unsigned short encoding;
  1361. {
  1362.   *len = 0;            /* in case we return an error */
  1363.   if (encoding == ENCBINARY) {    /* unmodified binary */
  1364.     if ((*len = srcl) > *dstl) {/* resize if not enough space */
  1365.       fs_give ((void **) dst);    /* fs_resize does an unnecessary copy */
  1366.       *dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
  1367.     }
  1368.                 /* copy that many bytes */
  1369.     memcpy (*dst,src,(size_t) srcl);
  1370.     *(*dst + srcl) = '\0';    /* tie off destination */
  1371.   }
  1372.                 /* all other cases return strcrlfcpy version */
  1373.   else *len = strcrlfcpy (dst,dstl,src,srcl);
  1374.   return *dst;            /* return the string */
  1375. }
  1376.  
  1377.  
  1378. /* Output RFC 822 message
  1379.  * Accepts: temporary buffer
  1380.  *        envelope
  1381.  *        body
  1382.  *        I/O routine
  1383.  *        stream for I/O routine
  1384.  * Returns: T if successful, NIL if failure
  1385.  */
  1386.  
  1387. long rfc822_output (t,env,body,f,s)
  1388.     char *t;
  1389.     ENVELOPE *env;
  1390.     BODY *body;
  1391.     soutr_t f;
  1392.     TCPSTREAM *s;
  1393. {
  1394.   rfc822_header (t,env,body);    /* build RFC822 header */
  1395.                 /* output header and body */
  1396.   return (*f) (s,t) && (body ? rfc822_output_body (body,f,s) : T);
  1397. }
  1398.  
  1399. /* Encode a body for 7BIT transmittal
  1400.  * Accepts: envelope
  1401.  *        body
  1402.  */
  1403.  
  1404. void rfc822_encode_body_7bit (env,body)
  1405.     ENVELOPE *env;
  1406.     BODY *body;
  1407. {
  1408.   void *f;
  1409.   PART *part;
  1410.   if (body) switch (body->type) {
  1411.   case TYPEMULTIPART:        /* multi-part */
  1412.     if (!body->parameter) {    /* cookie not set up yet? */
  1413.       char tmp[MAILTMPLEN];    /* make cookie not in BASE64 or QUOTEPRINT*/
  1414.       sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0),
  1415.            getpid ());
  1416.       body->parameter = mail_newbody_parameter ();
  1417.       body->parameter->attribute = cpystr ("BOUNDARY");
  1418.       body->parameter->value = cpystr (tmp);
  1419.     }
  1420.     part = body->contents.part;    /* encode body parts */
  1421.     do rfc822_encode_body_7bit (env,&part->body);
  1422.     while (part = part->next);    /* until done */
  1423.     break;
  1424.   case TYPEMESSAGE:        /* encapsulated message */
  1425.     switch (body->encoding) {
  1426.     case ENC7BIT:
  1427.       break;
  1428.     case ENC8BIT:
  1429.       mm_log ("8-bit included message in 7-bit message body",WARN);
  1430.       break;
  1431.     case ENCBINARY:
  1432.       mm_log ("Binary included message in 7-bit message body",WARN);
  1433.       break;
  1434.     default:
  1435.       fatal ("Invalid rfc822_encode_body_7bit message encoding");
  1436.     }
  1437.     break;            /* can't change encoding */
  1438.   default:            /* all else has some encoding */
  1439.     switch (body->encoding) {
  1440.     case ENC8BIT:        /* encode 8BIT into QUOTED-PRINTABLE */
  1441.                 /* remember old 8-bit contents */
  1442.       f = (void *) body->contents.text;
  1443.       body->contents.text = rfc822_8bit (body->contents.text,body->size.bytes,
  1444.                      &body->size.bytes);
  1445.       body->encoding = ENCQUOTEDPRINTABLE;
  1446.       fs_give (&f);        /* flush old binary contents */
  1447.       break;
  1448.     case ENCBINARY:        /* encode binary into BASE64 */
  1449.       f = body->contents.binary;/* remember old binary contents */
  1450.       body->contents.text = rfc822_binary (body->contents.binary,
  1451.                        body->size.bytes,&body->size.bytes);
  1452.       body->encoding = ENCBASE64;
  1453.       fs_give (&f);        /* flush old binary contents */
  1454.     default:            /* otherwise OK */
  1455.       break;
  1456.     }
  1457.     break;
  1458.   }
  1459. }
  1460.  
  1461. /* Encode a body for 8BIT transmittal
  1462.  * Accepts: envelope
  1463.  *        body
  1464.  */
  1465.  
  1466. void rfc822_encode_body_8bit (env,body)
  1467.     ENVELOPE *env;
  1468.     BODY *body;
  1469. {
  1470.   void *f;
  1471.   PART *part;
  1472.   if (body) switch (body->type) {
  1473.   case TYPEMULTIPART:        /* multi-part */
  1474.     if (!body->parameter) {    /* cookie not set up yet? */
  1475.       char tmp[MAILTMPLEN];    /* make cookie not in BASE64 or QUOTEPRINT*/
  1476.       sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0),
  1477.            getpid ());
  1478.       body->parameter = mail_newbody_parameter ();
  1479.       body->parameter->attribute = cpystr ("BOUNDARY");
  1480.       body->parameter->value = cpystr (tmp);
  1481.     }
  1482.     part = body->contents.part;    /* encode body parts */
  1483.     do rfc822_encode_body_8bit (env,&part->body);
  1484.     while (part = part->next);    /* until done */
  1485.     break;
  1486.   case TYPEMESSAGE:        /* encapsulated message */
  1487.     switch (body->encoding) {
  1488.     case ENC7BIT:
  1489.     case ENC8BIT:
  1490.       break;
  1491.     case ENCBINARY:
  1492.       mm_log ("Binary included message in 8-bit message body",WARN);
  1493.       break;
  1494.     default:
  1495.       fatal ("Invalid rfc822_encode_body_7bit message encoding");
  1496.     }
  1497.     break;            /* can't change encoding */
  1498.   default:            /* other type, encode binary into BASE64 */
  1499.     if (body->encoding == ENCBINARY) {
  1500.       f = body->contents.binary;/* remember old binary contents */
  1501.       body->contents.text = rfc822_binary (body->contents.binary,
  1502.                        body->size.bytes,&body->size.bytes);
  1503.       body->encoding = ENCBASE64;
  1504.       fs_give (&f);        /* flush old binary contents */
  1505.     }
  1506.     break;
  1507.   }
  1508. }
  1509.  
  1510. /* Output RFC 822 body
  1511.  * Accepts: body
  1512.  *        I/O routine
  1513.  *        stream for I/O routine
  1514.  * Returns: T if successful, NIL if failure
  1515.  */
  1516.  
  1517. long rfc822_output_body (body,f,s)
  1518.     BODY *body;
  1519.     soutr_t f;
  1520.     TCPSTREAM *s;
  1521. {
  1522.   PART *part;
  1523.   PARAMETER *param;
  1524.   char *cookie = NIL;
  1525.   char tmp[MAILTMPLEN];
  1526.   char *t;
  1527.   switch (body->type) {
  1528.   case TYPEMULTIPART:        /* multipart gets special handling */
  1529.     part = body->contents.part;    /* first body part */
  1530.                 /* find cookie */
  1531.     for (param = body->parameter; param && !cookie; param = param->next)
  1532.       if (!strcmp (param->attribute,"BOUNDARY")) cookie = param->value;
  1533.     if (!cookie) cookie = "-";    /* yucky default */
  1534.     do {            /* for each part */
  1535.                 /* build cookie */
  1536.       sprintf (t = tmp,"--%s\015\012",cookie);
  1537.                 /* append mini-header */
  1538.       rfc822_write_body_header (&t,&part->body);
  1539.       strcat (t,"\015\012");    /* write terminating blank line */
  1540.                 /* output cookie, mini-header, and contents */
  1541.       if (!((*f) (s,tmp) && rfc822_output_body (&part->body,f,s))) return NIL;
  1542.     } while (part = part->next);/* until done */
  1543.                 /* output trailing cookie */
  1544.     sprintf (t = tmp,"--%s--",cookie);
  1545.     break;
  1546.   case TYPEMESSAGE:        /* encapsulated message */
  1547.     t = body->contents.msg.text;
  1548.     break;
  1549.   default:            /* all else is text now */
  1550.     t = (char *) body->contents.text;
  1551.     break;
  1552.   }
  1553.                 /* output final stuff */
  1554.   if (t && *t && !((*f) (s,t) && (*f) (s,"\015\012"))) return NIL;
  1555.   return LONGT;
  1556. }
  1557.  
  1558. /* Convert BASE64 contents to binary
  1559.  * Accepts: source
  1560.  *        length of source
  1561.  *        pointer to return destination length
  1562.  * Returns: destination as binary
  1563.  */
  1564.  
  1565. void *rfc822_base64 (src,srcl,len)
  1566.     unsigned char *src;
  1567.     unsigned long srcl;
  1568.     unsigned long *len;
  1569. {
  1570.   char c;
  1571.   void *ret = fs_get ((size_t) (*len = 4 + ((srcl * 3) / 4)));
  1572.   char *d = (char *) ret;
  1573.   short e = 0;
  1574.   memset (ret,0,(size_t) *len);    /* initialize block */
  1575.   *len = 0;            /* in case we return an error */
  1576.   while (srcl--) {        /* until run out of characters */
  1577.     c = *src++;            /* simple-minded decode */
  1578.     if (isupper (c)) c -= 'A';
  1579.     else if (islower (c)) c -= 'a' - 26;
  1580.     else if (isdigit (c)) c -= '0' - 52;
  1581.     else if (c == '+') c = 62;
  1582.     else if (c == '/') c = 63;
  1583.     else if (c == '=') {    /* padding */
  1584.       switch (e++) {        /* check quantum position */
  1585.       case 2:
  1586.     if (*src != '=') return NIL;
  1587.     break;
  1588.       case 3:
  1589.     e = 0;            /* restart quantum */
  1590.     break;
  1591.       default:            /* impossible quantum position */
  1592.     fs_give (&ret);
  1593.     return NIL;
  1594.       }
  1595.       continue;
  1596.     }
  1597.     else continue;        /* junk character */
  1598.     switch (e++) {        /* install based on quantum position */
  1599.     case 0:
  1600.       *d = c << 2;        /* byte 1: high 6 bits */
  1601.       break;
  1602.     case 1:
  1603.       *d++ |= c >> 4;        /* byte 1: low 2 bits */
  1604.       *d = c << 4;        /* byte 2: high 4 bits */
  1605.       break;
  1606.     case 2:
  1607.       *d++ |= c >> 2;        /* byte 2: low 4 bits */
  1608.       *d = c << 6;        /* byte 3: high 2 bits */
  1609.       break;
  1610.     case 3:
  1611.       *d++ |= c;        /* byte 3: low 6 bits */
  1612.       e = 0;            /* reinitialize mechanism */
  1613.       break;
  1614.     }
  1615.   }
  1616.   *len = d - (char *) ret;    /* calculate data length */
  1617.   return ret;            /* return the string */
  1618. }
  1619.  
  1620. /* Convert binary contents to BASE64
  1621.  * Accepts: source
  1622.  *        length of source
  1623.  *        pointer to return destination length
  1624.  * Returns: destination as BASE64
  1625.  */
  1626.  
  1627. unsigned char *rfc822_binary (src,srcl,len)
  1628.     void *src;
  1629.     unsigned long srcl;
  1630.     unsigned long *len;
  1631. {
  1632.   unsigned char *ret,*d;
  1633.   unsigned char *s = (unsigned char *) src;
  1634.   char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  1635.   unsigned long i = ((srcl + 2) / 3) * 4;
  1636.   *len = i += 2 * ((i / 60) + 1);
  1637.   d = ret = (unsigned char *) fs_get ((size_t) ++i);
  1638.   for (i = 0; srcl; s += 3) {    /* process tuplets */
  1639.     *d++ = v[s[0] >> 2];    /* byte 1: high 6 bits (1) */
  1640.                 /* byte 2: low 2 bits (1), high 4 bits (2) */
  1641.     *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
  1642.                 /* byte 3: low 4 bits (2), high 2 bits (3) */
  1643.     *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
  1644.                 /* byte 4: low 6 bits (3) */
  1645.     *d++ = srcl ? v[s[2] & 0x3f] : '=';
  1646.     if (srcl) srcl--;        /* count third character if processed */
  1647.     if ((++i) == 15) {        /* output 60 characters? */
  1648.       i = 0;            /* restart line break count, insert CRLF */
  1649.       *d++ = '\015'; *d++ = '\012';
  1650.     }
  1651.   }
  1652.   *d++ = '\015'; *d++ = '\012';    /* insert final CRLF */
  1653.   *d = '\0';            /* tie off string */
  1654.   if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
  1655.   return ret;            /* return the resulting string */
  1656. }
  1657.  
  1658. /* Convert QUOTED-PRINTABLE contents to 8BIT
  1659.  * Accepts: source
  1660.  *        length of source
  1661.  *         pointer to return destination length
  1662.  * Returns: destination as 8-bit text
  1663.  */
  1664.  
  1665. unsigned char *rfc822_qprint (src,srcl,len)
  1666.     unsigned char *src;
  1667.     unsigned long srcl;
  1668.                        unsigned long *len;
  1669. {
  1670.   unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
  1671.   unsigned char *d = ret;
  1672.   unsigned char *s = d;
  1673.   unsigned char c,e;
  1674.   *len = 0;            /* in case we return an error */
  1675.   src[srcl] = '\0';        /* make sure string tied off */
  1676.   while (c = *src++) {        /* until run out of characters */
  1677.     switch (c) {        /* what type of character is it? */
  1678.     case '=':            /* quoting character */
  1679.       switch (c = *src++) {    /* what does it quote? */
  1680.       case '\0':        /* end of data */
  1681.     src--;            /* back up pointer */
  1682.     break;
  1683.       case '\015':        /* non-significant line break */
  1684.     s = d;            /* accept any leading spaces */
  1685.     if (*src == '\012') src++;
  1686.     break;
  1687.       default:            /* two hex digits then */
  1688.     if (!isxdigit (c)) {    /* must be hex! */
  1689.       fs_give ((void **) &ret);
  1690.       return NIL;
  1691.     }
  1692.     if (isdigit (c)) e = c - '0';
  1693.     else e = c - (isupper (c) ? 'A' - 10 : 'a' - 10);
  1694.     c = *src++;        /* snarf next character */
  1695.     if (!isxdigit (c)) {    /* must be hex! */
  1696.       fs_give ((void **) &ret);
  1697.       return NIL;
  1698.     }
  1699.     if (isdigit (c)) c -= '0';
  1700.     else c -= (isupper (c) ? 'A' - 10 : 'a' - 10);
  1701.     *d++ = c + (e << 4);    /* merge the two hex digits */
  1702.     s = d;            /* note point of non-space */
  1703.     break;
  1704.       }
  1705.       break;
  1706.     case ' ':            /* space, possibly bogus */
  1707.       *d++ = c;            /* stash the space but don't update s */
  1708.       break;
  1709.     case '\015':        /* end of line */
  1710.       d = s;            /* slide back to last non-space, drop in */
  1711.     default:
  1712.       *d++ = c;            /* stash the character */
  1713.       s = d;            /* note point of non-space */
  1714.     }
  1715.   }
  1716.   *d = '\0';            /* tie off results */
  1717.   *len = d - ret;        /* calculate length */
  1718.   return ret;            /* return the string */
  1719. }
  1720.  
  1721. /* Convert 8BIT contents to QUOTED-PRINTABLE
  1722.  * Accepts: source
  1723.  *        length of source
  1724.  *         pointer to return destination length
  1725.  * Returns: destination as quoted-printable text
  1726.  */
  1727.  
  1728. #define MAXL (size_t) 75    /* 76th position only used by continuation = */
  1729.  
  1730. unsigned char *rfc822_8bit (src,srcl,len)
  1731.     unsigned char *src;
  1732.     unsigned long srcl;
  1733.                      unsigned long *len;
  1734. {
  1735.   unsigned long lp = 0;
  1736.   unsigned char *ret = (unsigned char *)
  1737.     fs_get ((((size_t) srcl) * (size_t) 3) + (((size_t) srcl) / MAXL) +
  1738.         (size_t) 2);
  1739.   unsigned char *d = ret;
  1740.   char *hex = "0123456789ABCDEF";
  1741.   unsigned char c;
  1742.   while (srcl--) {        /* for each character */
  1743.                 /* true line break? */
  1744.     if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
  1745.       *d++ = '\015'; *d++ = *src++; srcl--;
  1746.       lp = 0;            /* reset line count */
  1747.     }
  1748.     else {            /* not a line break */
  1749.                 /* quoting required? */
  1750.       if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
  1751.       ((c == ' ') && (*src == '\015'))) {
  1752.     if ((lp += 3) > MAXL) {    /* yes, would line overflow? */
  1753.       *d++ = '='; *d++ = '\015'; *d++ = '\012';
  1754.       lp = 3;        /* set line count */
  1755.     }
  1756.     *d++ = '=';        /* quote character */
  1757.     *d++ = hex[c >> 4];    /* high order 4 bits */
  1758.     *d++ = hex[c & 0xf];    /* low order 4 bits */
  1759.       }
  1760.       else {            /* ordinary character */
  1761.     if ((++lp) > MAXL) {    /* would line overflow? */
  1762.       *d++ = '='; *d++ = '\015'; *d++ = '\012';
  1763.       lp = 1;        /* set line count */
  1764.     }
  1765.     *d++ = c;        /* ordinary character */
  1766.       }
  1767.     }
  1768.   }
  1769.   *d = '\0';            /* tie off destination */
  1770.   *len = d - ret;        /* calculate true size */
  1771.                 /* try to give some space back */
  1772.   fs_resize ((void **) &ret,(size_t) *len + 1);
  1773.   return ret;
  1774. }
  1775.